//! Module for the chess board representation and operations
//!
//! This module provides the board state representation and basic board operations
//! for the Chinese Chess game.

use std::fmt;
use serde::{Serialize, Deserialize};
use crate::{Position, Side, ChessResult, ChessError};
use crate::piece::{Piece, PieceType};

/// Represents the game board
#[derive(Clone, Serialize, Deserialize)]
pub struct Board {
    /// The actual board representation, None means empty square
    squares: [[Option<Piece>; 9]; 10],
}

impl Board {
    /// Create a new empty board
    pub fn new() -> Self {
        Board {
            squares: [[None; 9]; 10],
        }
    }

    /// Create a new board with initial piece placement
    pub fn new_with_pieces() -> Self {
        let mut board = Board::new();
        
        // Place pieces for both sides
        for side in [Side::Red, Side::Black] {
            let (rank_1, rank_2, rank_3) = match side {
                Side::Red => (0, 2, 3),
                Side::Black => (9, 7, 6),
            };

            // Place main pieces on the back rank
            board.place_piece(Position::new(0, rank_1).unwrap(), Piece::new(PieceType::Chariot, side));
            board.place_piece(Position::new(1, rank_1).unwrap(), Piece::new(PieceType::Horse, side));
            board.place_piece(Position::new(2, rank_1).unwrap(), Piece::new(PieceType::Elephant, side));
            board.place_piece(Position::new(3, rank_1).unwrap(), Piece::new(PieceType::Advisor, side));
            board.place_piece(Position::new(4, rank_1).unwrap(), Piece::new(PieceType::General, side));
            board.place_piece(Position::new(5, rank_1).unwrap(), Piece::new(PieceType::Advisor, side));
            board.place_piece(Position::new(6, rank_1).unwrap(), Piece::new(PieceType::Elephant, side));
            board.place_piece(Position::new(7, rank_1).unwrap(), Piece::new(PieceType::Horse, side));
            board.place_piece(Position::new(8, rank_1).unwrap(), Piece::new(PieceType::Chariot, side));

            // Place cannons
            board.place_piece(Position::new(1, rank_2).unwrap(), Piece::new(PieceType::Cannon, side));
            board.place_piece(Position::new(7, rank_2).unwrap(), Piece::new(PieceType::Cannon, side));

            // Place soldiers/pawns
            for file in (0..9).step_by(2) {
                board.place_piece(Position::new(file, rank_3).unwrap(), Piece::new(PieceType::Soldier, side));
            }
        }

        board
    }

    /// Get piece at the given position
    pub fn get_piece(&self, pos: Position) -> Option<Piece> {
        if !pos.is_valid() {
            return None;
        }
        self.squares[pos.rank as usize][pos.file as usize]
    }

    /// Place a piece at the given position
    pub fn place_piece(&mut self, pos: Position, piece: Piece) {
        if pos.is_valid() {
            self.squares[pos.rank as usize][pos.file as usize] = Some(piece);
        }
    }

    /// Remove and return the piece at the given position
    pub fn remove_piece(&mut self, pos: Position) -> Option<Piece> {
        if pos.is_valid() {
            let piece = self.squares[pos.rank as usize][pos.file as usize];
            self.squares[pos.rank as usize][pos.file as usize] = None;
            piece
        } else {
            None
        }
    }

    /// Move a piece from one position to another
    pub fn move_piece(&mut self, from: Position, to: Position) -> ChessResult<()> {
        if !from.is_valid() || !to.is_valid() {
            return Err(ChessError::InvalidPosition);
        }

        let piece = self.remove_piece(from)
            .ok_or(ChessError::InvalidMove)?;
        
        self.place_piece(to, piece);
        Ok(())
    }

    /// Check if a position is empty
    pub fn is_empty(&self, pos: Position) -> bool {
        self.get_piece(pos).is_none()
    }

    /// Find the position of a specific piece type for a given side
    pub fn find_piece(&self, piece_type: PieceType, side: Side) -> Option<Position> {
        for rank in 0..10 {
            for file in 0..9 {
                let pos = Position { file, rank };
                if let Some(piece) = self.get_piece(pos) {
                    if piece.piece_type == piece_type && piece.side == side {
                        return Some(pos);
                    }
                }
            }
        }
        None
    }

    /// Get all pieces of a given side
    pub fn get_pieces(&self, side: Side) -> Vec<(Position, Piece)> {
        let mut pieces = Vec::new();
        for rank in 0..10 {
            for file in 0..9 {
                let pos = Position { file, rank };
                if let Some(piece) = self.get_piece(pos) {
                    if piece.side == side {
                        pieces.push((pos, piece));
                    }
                }
            }
        }
        pieces
    }

    /// Convert board state to FEN-like string (for AI training)
    pub fn to_fen(&self) -> String {
        let mut fen = String::new();
        for rank in (0..10).rev() {
            let mut empty_count = 0;
            
            for file in 0..9 {
                match self.squares[rank][file] {
                    Some(piece) => {
                        if empty_count > 0 {
                            fen.push_str(&empty_count.to_string());
                            empty_count = 0;
                        }
                        fen.push(piece.to_char());
                    }
                    None => empty_count += 1,
                }
            }
            
            if empty_count > 0 {
                fen.push_str(&empty_count.to_string());
            }
            
            if rank > 0 {
                fen.push('/');
            }
        }
        fen
    }
}

impl fmt::Display for Board {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        writeln!(f, "  ０１２３４５６７８")?;
        writeln!(f, "  ─────────────")?;
        
        for rank in (0..10).rev() {
            write!(f, "{} │", rank)?;
            for file in 0..9 {
                match self.squares[rank][file] {
                    Some(piece) => write!(f, "{}", piece.to_char())?,
                    None => write!(f, "．")?,
                }
            }
            writeln!(f)?;
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_new_board() {
        let board = Board::new();
        assert!(board.is_empty(Position { file: 0, rank: 0 }));
    }

    #[test]
    fn test_place_and_get_piece() {
        let mut board = Board::new();
        let pos = Position { file: 0, rank: 0 };
        let piece = Piece::new(PieceType::General, Side::Red);
        
        board.place_piece(pos, piece);
        assert_eq!(board.get_piece(pos), Some(piece));
    }

    #[test]
    fn test_move_piece() {
        let mut board = Board::new();
        let from = Position { file: 0, rank: 0 };
        let to = Position { file: 0, rank: 1 };
        let piece = Piece::new(PieceType::General, Side::Red);
        
        board.place_piece(from, piece);
        assert!(board.move_piece(from, to).is_ok());
        assert!(board.is_empty(from));
        assert_eq!(board.get_piece(to), Some(piece));
    }

    #[test]
    fn test_initial_board_setup() {
        let board = Board::new_with_pieces();
        
        // Test red side pieces
        assert_eq!(
            board.get_piece(Position { file: 0, rank: 0 }),
            Some(Piece::new(PieceType::Chariot, Side::Red))
        );
        assert_eq!(
            board.get_piece(Position { file: 4, rank: 0 }),
            Some(Piece::new(PieceType::General, Side::Red))
        );
        
        // Test black side pieces
        assert_eq!(
            board.get_piece(Position { file: 0, rank: 9 }),
            Some(Piece::new(PieceType::Chariot, Side::Black))
        );
        assert_eq!(
            board.get_piece(Position { file: 4, rank: 9 }),
            Some(Piece::new(PieceType::General, Side::Black))
        );
    }

    #[test]
    fn test_find_piece() {
        let board = Board::new_with_pieces();
        let red_general_pos = board.find_piece(PieceType::General, Side::Red);
        assert_eq!(red_general_pos, Some(Position { file: 4, rank: 0 }));
    }
}